﻿using System;
using System.Net;
using System.Linq;
using System.Xml.Serialization;
using System.IO;
using System.Data;
using System.Collections.Generic;
using System.ServiceModel.Description;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using TransactionServer.General_Classes;
using ConditionOperator = Microsoft.Xrm.Sdk.Query.ConditionOperator;
using Connector = TransactionServer.General_Classes;
using Entity = Microsoft.Xrm.Sdk.Entity;

namespace TransactionServer.CRMActivities
{

    public class CRMConnector : Connector.ISystemConnector
    {
        public string connection;
        public object syncRoot = new object();
        public object syncRoot1 = new object();

        private CRMEntityMetadataCache _entityCache;

        private CRMEntityMetadataCache entityCache
        {
            get
            {
                lock (syncRoot)
                {
                    if (_entityCache == null)
                    {
                        _entityCache = new CRMEntityMetadataCache(this);
                    }
                    return _entityCache;
                }
            }
        }

        private Microsoft.Xrm.Sdk.IOrganizationService _crmService;

        public CRMConnector(Dictionary<string, string> settings)
            : base(settings)
        {
        }

        public override Connector.Event getNextEvent()
        {
            var queryEvent = new QueryExpression()
                                 {
                                     ColumnSet = new ColumnSet() {AllColumns = true},
                                     EntityName = "uds_integrationevent",
                                     PageInfo = new PagingInfo()
                                                    {
                                                        PageNumber = 1,
                                                        Count = 1
                                                    }
                                 };

            var integrationEvents = crmService.RetrieveMultiple(queryEvent).Entities;

            if (integrationEvents.Count > 0)
            {
                string xml = (string) integrationEvents[0]["uds_event"];
                StringReader sr = new StringReader(xml);
                var serializer = new XmlSerializer(typeof (Connector.Event));
                crmService.Delete("uds_integrationevent", (Guid) integrationEvents[0]["uds_integrationeventid"]);
                return (Connector.Event) serializer.Deserialize(sr);
            }
            else
            {
                return null;
            }
        }

        private int? _languageCode = null;

        public int languageCode
        {
            get { return 1049; }
        }

        public Microsoft.Xrm.Sdk.IOrganizationService crmService
        {
            get
            {
                lock (syncRoot)
                {
                    if (_crmService == null)
                    {
                        lock (this)
                        {
                            ServicePointManager.ServerCertificateValidationCallback += delegate {return true;};

                            ServerConnection serverConnect = new ServerConnection();
                            ServerConnection.Configuration serverConfig = serverConnect.GetServerConfiguration(settings);

                            _crmService = new OrganizationServiceProxy(serverConfig.OrganizationUri,
                                                                       serverConfig.HomeRealmUri,
                                                                       serverConfig.Credentials,
                                                                       serverConfig.DeviceCredentials);

                        }
                    }
                    // Refresh security token if it espires soon
                    else if (null != ((OrganizationServiceProxy)_crmService).SecurityTokenResponse &&
                                DateTime.UtcNow.AddMinutes(15) >= ((OrganizationServiceProxy)_crmService).SecurityTokenResponse.Response.Lifetime.Expires)
                    {
                        ((OrganizationServiceProxy)_crmService).Authenticate();
                    }

                    return _crmService;
                }
            }
        }

        public override List<string> getAttributes(string entity)
        {
            List<string> res = new List<string>();

            try
            {
                foreach (AttributeMetadata emd in entityCache[entity].Attributes)
                {
                    res.Add(emd.LogicalName.ToLower());
                }
            }
            catch
            {
            }

            return res;
        }

        public int getAttributePicklistValue(string entity, string attribute, string optionset, string value,
                                             bool create)
        {

            var response = (RetrieveAttributeResponse) crmService.Execute(new RetrieveAttributeRequest()
                                                                              {
                                                                                  EntityLogicalName = entity,
                                                                                  LogicalName = attribute
                                                                              });

            var options = ((PicklistAttributeMetadata) response.AttributeMetadata).OptionSet.Options.ToArray();

            var resultOptions =
                (from option in options where option.Label.UserLocalizedLabel.Label == value select option).ToList();

            if (resultOptions.Count <= 0)
            {
                if (create)
                {
                    var request = new InsertOptionValueRequest()
                                      {
                                          Label = new Label(value, languageCode)
                                      };

                    string xml = string.Empty;
                    if (!String.IsNullOrEmpty(optionset))
                    {
                        request.OptionSetName = optionset;
                        xml =
                            String.Format(
                                "<importexportxml><optionsets><optionset>{0}</optionset></optionsets></importexportxml>",
                                optionset);
                    }
                    else
                    {
                        request.EntityLogicalName = entity;
                        request.AttributeLogicalName = attribute;
                        xml =
                            String.Format(
                                "<importexportxml><entities><entity>{0}</entity></entities></importexportxml>", entity);
                    }
                    ;

                    int result = ((InsertOptionValueResponse) crmService.Execute(request)).NewOptionValue;

                    crmService.Execute(new PublishXmlRequest()
                                           {
                                               ParameterXml = xml
                                           });

                    return result;
                }
                else
                {
                    return -1;
                }
            }
            else
            {
                return resultOptions[0].Value.Value;
            }

        }

        public string lookupEntityByField(string entity, string field, object value)
        {
            EntityMetadata emd = entityCache[entity];

            var result = String.Empty;

            var cs = new ColumnSet(new string[] {emd.PrimaryIdAttribute});

            var qbaEntity = new QueryByAttribute()
                                {
                                    ColumnSet = cs,
                                    EntityName = entity
                                };

            qbaEntity.Attributes.Add(field);
            qbaEntity.Values.Add(value);

            var request = new RetrieveMultipleRequest()
                              {
                                  Query = qbaEntity
                              };

            var bec = ((RetrieveMultipleResponse) crmService.Execute(request)).EntityCollection;

            if (bec.Entities.Count > 0)
            {
                result = ((Guid) ((Entity) bec.Entities[0])[emd.PrimaryIdAttribute]).ToString();
            }

            return result;
        }

        public override List<string> getEntities()
        {
            List<string> res = new List<string>();

            RetrieveAllEntitiesRequest request = new RetrieveAllEntitiesRequest();
            request.EntityFilters = EntityFilters.Entity;
            RetrieveAllEntitiesResponse response = (RetrieveAllEntitiesResponse) crmService.Execute(request);

            foreach (EntityMetadata emd in response.EntityMetadata)
            {
                res.Add(emd.LogicalName);
            }

            return res;
        }

        private AttributeMetadata getAttrByName(EntityMetadata emd, string attrName)
        {
            return (from amd in emd.Attributes where amd.LogicalName == attrName select amd).First();
        }

        private OneToManyRelationshipMetadata getRelationshipByName(EntityMetadata emd, string attrName)
        {
            return
                (from amd in emd.ManyToOneRelationships where amd.ReferencingAttribute == attrName select amd).First();
        }

        public List<Connector.Entity> getObjects(Connector.Filter filter)
        {
            var result = new List<Connector.Entity>();

            var qeObjects = new QueryExpression() {EntityName = filter.entity};

            ColumnSet cs = new ColumnSet();
            foreach (Connector.CAttribute attribute in filter.attributes)
            {
                cs.AddColumn(attribute.name);
            }
            qeObjects.ColumnSet = cs;

            if (filter.conditions.Count > 0)
            {
                var feObjects = new FilterExpression() {FilterOperator = LogicalOperator.And};

                foreach (Connector.CCondition condition in filter.conditions)
                {
                    var ceObjects = new ConditionExpression()
                                        {
                                            AttributeName = condition.attribute
                                        };
                    ceObjects.Values.Add(condition.value);

                    switch (condition.opr)
                    {
                        case Connector.ConditionOperator.Equal:
                            ceObjects.Operator = ConditionOperator.Equal;
                            break;
                        case Connector.ConditionOperator.Less:
                            ceObjects.Operator = ConditionOperator.LessThan;
                            break;
                        case Connector.ConditionOperator.LessEqual:
                            ceObjects.Operator = ConditionOperator.LessEqual;
                            break;
                        case Connector.ConditionOperator.More:
                            ceObjects.Operator = ConditionOperator.GreaterThan;
                            break;
                        case Connector.ConditionOperator.MoreEqual:
                            ceObjects.Operator = ConditionOperator.GreaterEqual;
                            break;
                        case Connector.ConditionOperator.NotEqual:
                            ceObjects.Operator = ConditionOperator.NotEqual;
                            break;
                        default:
                            break;
                    }

                    feObjects.Conditions.Add(ceObjects);
                }

                qeObjects.Criteria = feObjects;
            }

            var request = new RetrieveMultipleRequest()
                              {
                                  Query = qeObjects
                              };

            var response = (RetrieveMultipleResponse) crmService.Execute(request);

            var emd = entityCache[filter.entity];

            foreach (Entity de in response.EntityCollection.Entities)
            {
                var ent = new Connector.Entity()
                              {
                                  System = filter.connection,
                                  Name = de.LogicalName
                              };

                foreach (KeyValuePair<string, object> prop in de.Attributes)
                {
                    AttributeMetadata amd = getAttrByName(emd, prop.Key);

                    if (amd != null)
                    {
                        ent[prop.Key] = prop.Value.ToString();

                        if (amd.AttributeType.Value == AttributeTypeCode.Uniqueidentifier)
                        {
                            ent.Id = prop.Value.ToString();
                        }

                        /*                        switch (amd.AttributeType.Value)
                                                {
                                                    case AttributeTypeCode.Boolean:
                                                        ent[prop.Key] = prop.Value.ToString();
                                                        break;
                                                    case AttributeTypeCode.CalendarRules:
                                                        break;
                                                    case AttributeTypeCode.Customer:
                                                        ent.Add(prop.Name, ((CustomerProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.DateTime:
                                                        ent.Add(prop.Name, ((CrmDateTimeProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.Decimal:
                                                        ent.Add(prop.Name, ((CrmDecimalProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.Float:
                                                        ent.Add(prop.Name, ((CrmFloatProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.Integer:
                                                        ent.Add(prop.Name, ((CrmNumberProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.Internal:
                                                        break;
                                                    case AttributeTypeCode.Lookup:
                                                        ent.Add(prop.Name, ((LookupProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.Memo:
                                                        ent.Add(prop.Name, ((StringProperty)prop).Value);
                                                        break;
                                                    case AttributeTypeCode.Money:
                                                        ent.Add(prop.Name, ((CrmMoneyProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.Owner:
                                                        ent.Add(prop.Name, ((OwnerProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.PartyList:
                                                        break;
                                                    case AttributeTypeCode.Picklist:
                                                        ent.Add(prop.Name, ((PicklistProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.PrimaryKey:
                                                        ent.Add(prop.Name, ((KeyProperty)prop).Value.Value.ToString());
                                                        ent.Id = ((KeyProperty)prop).Value.Value.ToString();
                                                        break;
                                                    case AttributeTypeCode.State:
                                                        ent.Add(prop.Name, ((StateProperty)prop).Value);
                                                        break;
                                                    case AttributeTypeCode.Status:
                                                        ent.Add(prop.Name, ((StatusProperty)prop).Value.Value.ToString());
                                                        break;
                                                    case AttributeTypeCode.String:
                                                        ent.Add(prop.Name, ((StringProperty)prop).Value);
                                                        break;
                                                    case AttributeTypeCode.UniqueIdentifier:
                                                        break;
                                                    case AttributeTypeCode.Virtual:
                                                        break;
                                                    default:
                                                        break;
                                                }*/
                    }
                }

                result.Add(ent);
            }


            return result;
        }

        public Connector.Entity getEntityById(string systemName, string entityName, string entityId, string[] attributes)
        {
            var start = DateTime.Now;
            var de = crmService.Retrieve(entityName, new Guid(entityId), new ColumnSet(attributes));
            var end = DateTime.Now;
            var span = end - start;
            Console.WriteLine("Retrieve {0}", span.TotalMilliseconds);

            Connector.Entity ent = new Connector.Entity(systemName, entityName, entityId);

            Guid id = Guid.Empty;

            EntityMetadata emd = entityCache[entityName];

            foreach (KeyValuePair<string, object> prop in de.Attributes)
            {
                AttributeMetadata amd = getAttrByName(emd, prop.Key);

                if (amd != null)
                {
                    //                    ent[prop.Key] = prop.Value.ToString();
                    switch (amd.AttributeType.Value)
                    {
                        case AttributeTypeCode.Lookup:
                        case AttributeTypeCode.Owner:
                        case AttributeTypeCode.Customer:
                            ent[prop.Key] = ((EntityReference) prop.Value).Id.ToString();
                            break;
                        case AttributeTypeCode.Money:
                            ent[prop.Key] = ((Money) prop.Value).Value.ToString();
                            break;
                        case AttributeTypeCode.State:
                        case AttributeTypeCode.Status:
                        case AttributeTypeCode.Picklist:
                            ent[prop.Key] = ((OptionSetValue) prop.Value).Value.ToString();
                            break;
                        default:
                            ent[prop.Key] = prop.Value.ToString();
                            break;
                    }
                }
            }

            return ent;
        }

        public Entity ConvertFrom(Connector.Entity entity)
        {
            var result = new Entity(entity.Name);
            var emd = entityCache[entity.Name];

            if (!String.IsNullOrEmpty(entity.Id))
            {
                result[emd.PrimaryIdAttribute] = new Guid(entity.Id);
            }

            foreach (KeyValuePair<string, object> item in entity)
            {
                AttributeMetadata amd = getAttrByName(emd, item.Key);
                string value = (string) item.Value;
                if (amd != null)
                {
                    try
                    {
                        switch (amd.AttributeType.Value)
                        {
                            case AttributeTypeCode.Boolean:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = Boolean.Parse(value);
                                break;
                            case AttributeTypeCode.Customer:
                                if (!String.IsNullOrEmpty(value))
                                {
                                    var reference = new EntityReference()
                                                        {
                                                            Id = new Guid(value),
                                                            LogicalName = "account"
                                                        };

                                    result[item.Key] = reference;
                                }
                                break;
                            case AttributeTypeCode.DateTime:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = DateTime.Parse(value);
                                break;
                            case AttributeTypeCode.Decimal:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = Decimal.Parse(value);
                                break;
                            case AttributeTypeCode.Double:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = Double.Parse(value);
                                break;
                            case AttributeTypeCode.Integer:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = int.Parse(value);
                                break;
                            case AttributeTypeCode.Lookup:
                                if (!String.IsNullOrEmpty(value))
                                {
                                    OneToManyRelationshipMetadata rmd = getRelationshipByName(emd, item.Key);

                                    if (rmd != null)
                                    {
                                        var referencel = new EntityReference()
                                                             {
                                                                 Id = new Guid(value),
                                                                 LogicalName = rmd.ReferencedEntity
                                                             };

                                        result[item.Key] = referencel;
                                    }
                                }
                                break;
                            case AttributeTypeCode.Memo:
                                result[item.Key] = (string) value;
                                break;
                            case AttributeTypeCode.Money:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = new Money(Decimal.Parse(value));
                                break;
                            case AttributeTypeCode.Owner:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = new EntityReference("systemuser", new Guid((string) value));
                                break;
                            case AttributeTypeCode.State:
                            case AttributeTypeCode.Status:
                            case AttributeTypeCode.Picklist:
                                if (!String.IsNullOrEmpty(value))
                                    result[item.Key] = new OptionSetValue(int.Parse(value));
                                break;
                            case AttributeTypeCode.String:
                                result[item.Key] = (string) item.Value;
                                break;
                            default:
                                break;
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(String.Format("Error parcing value {0} of attribute {1} of type {2}", value,
                                                          item.Key, amd.AttributeType.Value));
                    }

                }
            }



            return result;
        }

        public override DataTable getObjectList(string entityName)
        {
            var result = new DataTable();
            result.Columns.Add("objectid");
            result.Columns.Add("name");

            var mdAttributes = entityCache[entityName];
            var nameField = mdAttributes.PrimaryNameAttribute;
            var keyField = mdAttributes.PrimaryIdAttribute;

            var csEntities = new ColumnSet();
            csEntities.AddColumns(nameField, keyField);

            var qeEntities = new QueryExpression()
                                 {
                                     EntityName = entityName,
                                     ColumnSet = csEntities
                                 };

            var requestEntities = new RetrieveMultipleRequest()
                                      {
                                          Query = qeEntities
                                      };

            var becEntities = ((RetrieveMultipleResponse) crmService.Execute(requestEntities)).EntityCollection.Entities;

            foreach (Entity deEntity in becEntities)
            {
                var row = result.NewRow();
                row["objectid"] = ((Guid) deEntity[keyField]).ToString();
                if (deEntity.Contains(nameField))
                {
                    row["name"] = (string) deEntity[nameField];
                }
                result.Rows.Add(row);
            }

            return result;
        }

        public override HealthInfo CheckHealth()
        {
            var healthInfo = new HealthInfo()
                                 {
                                     ServiceName = GetType().FullName
                                 };

            try
            {
                var whoAmIResponse = ((WhoAmIResponse) crmService.Execute(new WhoAmIRequest()));
                healthInfo.Status = true;
            }
            catch (Exception exc)
            {
                close();
                healthInfo.Status = false;
                healthInfo.ErrorInfo = string.Format("{0}\n{1}", exc.Message, exc.StackTrace);
            }

            return healthInfo;
        }

        public override void close()
        {
            _crmService = null;
            GC.Collect();
        }
    }
}